﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace PNet
{
    [System.Serializable]
    public abstract class IOCPMsg { };
    public class IOCPNet<T,K> where T : IOCPToken<K>,new() where K : IOCPMsg, new()
    {
        private Socket mSocket;
        private SocketAsyncEventArgs mArgs;
        private T mToken;
        public T Token { get => mToken; }
        public IOCPNet() 
        {
            mArgs = new SocketAsyncEventArgs();
            mArgs.Completed += IO_Completed;
    
        }

        #region Client
        public void StartAsClient(string url, int port)
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(url), port);
            mSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            mArgs.RemoteEndPoint = endPoint;
            IOCPTool.ColorLog(IOCPTool.LogColor.Green, "Client Start...");
            StartConnnect();
        }
        public void StartConnnect()
        {

            bool isPending = mSocket.ConnectAsync(mArgs);
            if (!isPending)
            {
                ProcessConnect();
            }
            else
            {
                IOCPTool.ColorLog(IOCPTool.LogColor.Green, "客户端连接挂起!");
            }
        }
        public void Disconnect()
        {
            IOCPTool.Log("Client is disconnect...");
        }

        public void ProcessConnect()
        {
            IOCPTool.ColorLog(IOCPTool.LogColor.Green, "客户端连接成功!");
            mToken = new T();
            mToken.InitToken(mSocket);
        }


        public void CloseClient()
        {
            Disconnect();
            if (mToken != null)
            {
                mToken.CloseToken();
            }
            if (mSocket != null)
            {
                mSocket = null;
            }

        }
        #endregion

        #region Server
        private int mBackLog = 100;
        private List<T> mTokenList;
        private IOCPTokenPool<T,K> mPool;
        private int mConnnectCount = 0;
        private Semaphore mSemaphore;

        public void StartAsServer(string ip, int port, int maxCount)
        {
            mSemaphore = new Semaphore(maxCount, maxCount);
            mConnnectCount = 0;
            mPool = new IOCPTokenPool<T,K>(maxCount);
            for (int i = 0; i < maxCount; i++)
            {
                T token = new T();
                token.TokenID = i;
                mPool.Push(token);
            }

            mTokenList = new List<T>();
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse(ip), port);
            mSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            mSocket.Bind(endPoint);
            mSocket.Listen(mBackLog);
            IOCPTool.ConsoleLog("Server Start...", IOCPTool.LogColor.Green);
            StartAccept();
        }

        public void StartAccept()
        {
            mSemaphore.WaitOne();
            mArgs.AcceptSocket = null;
            bool isPending = mSocket.AcceptAsync(mArgs);
            if (!isPending)
            {
                ProcessAccept();
            }
            else
            {
                IOCPTool.ConsoleLog("服务器等待连接...", IOCPTool.LogColor.Green);
            }
        }
        public void ProcessAccept()
        {
            T token = mPool.Pop();
            if (token != null)
            {
                Interlocked.Increment(ref mConnnectCount);
                lock (mTokenList)
                {
                    mTokenList.Add(token);
                }
                token.InitToken(mArgs.AcceptSocket);
                token.OnTokenClose = CloseToken;
                IOCPTool.Log("Token {0} is connect", token.TokenID);
                StartAccept();
            }

            IOCPTool.ConsoleLog("服务器连接成功...", IOCPTool.LogColor.Green);
        }

        public void CloseToken(int tokenID)
        {
            int id = -1;
            for (int i = 0; i < mTokenList.Count; i++)
            {
                if (mTokenList[i].TokenID == tokenID)
                {
                    id = i;
                    break;
                }
            }
            mPool.Push(mTokenList[id]);
            lock (mTokenList)
            {
                mTokenList.Remove(mTokenList[id]);
            }
            mSemaphore.Release();
            Interlocked.Decrement(ref mConnnectCount);

            if (id == -1)
            {
                IOCPTool.LogError("TokenId {} is cannot in tokenlist.", tokenID);
            }

        }
        public void CloseServer()
        {
            for (int i = 0; i < mTokenList.Count; i++)
            {
                mTokenList[i].CloseToken();
            }
            mTokenList = null;
            if (mSocket != null)
            {
                mSocket.Close();
                mSocket = null;
            }

        }

        public List<T> GetAllTokens()
        {
            return mTokenList;
        }

        #endregion



        private void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Connect:
                    ProcessConnect();
                    break;
                case SocketAsyncOperation.Accept:
                    ProcessAccept(); 
                    break;
                default:
                    IOCPTool.LogError("Socket operation error:"+e.LastOperation);
                    break;
            }
        }



    }
}
